package com.example.springboottest.example.javassist;

import com.example.springboottest.example.javassist.proxy.service.TicketService;
import com.example.springboottest.example.javassist.proxy.service.impl.Station;
import javassist.*;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * 参考文章
 * https://blog.csdn.net/luanlouis/article/details/24589193
 * 1、dynamicCreateClassByString 例子演示动态创建类
 * 2、createStationProxy 演示创建动态代理类，也是通过创建目标类的子类方式实现动态代理
 *
 * A、JDK代理是最简单方便的，只需要使用Proxy和InvocationHandler两个类，不过只能代理接口。
 * B、其次是CGLIB，也很方便，不过需要引入CGLIB的JAR包。
 * C、Javassist需要用用字符串拼接Java源代码，稍微会比较繁琐。
 * D、最麻烦的是ASM，需要手工写字节码，一般人可能还写不出来。
 */
public class JavassistTest {
    private static final String BYTECODE_PATH = "/Users/fangdaye/work/workspace/spring-boot-test/src/main/java/com/example/springboottest/example/javassist/";

    public static void main(String[] args) {
//        dynamicCreateClassByString();

        try {
            createStationProxy();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 通过字符串动态生成class
     */
    public static void dynamicCreateClassByString() {
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.makeClass("Hello");
        CtMethod ctMethod = null;
        try {
            ctMethod = CtNewMethod.make("" +
                    "public static void test(){" +
                    "System.out.println(\"Hello World dynamicCreateClass\");" +
                    "}", ctClass);
            ctClass.addMethod(ctMethod);
            Class aClass = ctClass.toClass();
            Object object = aClass.newInstance();
            Method m = aClass.getDeclaredMethod("test", null);
            m.invoke(object, null);
            //可以直接生成字节
            ctClass.writeFile(BYTECODE_PATH);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*
     *由于我们现在不希望静态地有StationProxy类存在，希望在代码中，动态生成器二进制代码，加载进来。
     *为此，使用Javassist开源框架，在代码中动态地生成StationProxy的字节码：
     * 手动创建字节码
     */
    public static void createStationProxy() throws Exception {
        ClassPool pool = ClassPool.getDefault();

        CtClass cc = pool.makeClass("com.example.springboottest.example.javassist.proxy.StationProxy");

        //设置接口
        CtClass interface1 = pool.get("com.example.springboottest.example.javassist.proxy.service.TicketService");
        cc.setInterfaces(new CtClass[]{interface1});

        //设置Field
        CtField field = CtField.make("private com.example.springboottest.example.javassist.proxy.service.impl.Station station;", cc);

        cc.addField(field);

        CtClass stationClass = pool.get("com.example.springboottest.example.javassist.proxy.service.impl.Station");
        CtClass[] arrays = new CtClass[]{stationClass};
        CtConstructor ctc = CtNewConstructor.make(arrays, null, CtNewConstructor.PASS_NONE, null, null, cc);
        //设置构造函数内部信息
        ctc.setBody("{this.station=$1;}");
        cc.addConstructor(ctc);

        //创建收取手续 takeHandlingFee方法
        CtMethod takeHandlingFee = CtMethod.make("private void takeHandlingFee() {}", cc);
        takeHandlingFee.setBody("System.out.println(\"收取手续费，打印发票。。。。。\");");
        cc.addMethod(takeHandlingFee);

        //创建showAlertInfo 方法
        CtMethod showInfo = CtMethod.make("private void showAlertInfo(String info) {}", cc);
        showInfo.setBody("System.out.println($1);");
        cc.addMethod(showInfo);

        //sellTicket
        CtMethod sellTicket = CtMethod.make("public void sellTicket(){}", cc);
        sellTicket.setBody("{this.showAlertInfo(\"××××您正在使用车票代售点进行购票，每张票将会收取5元手续费！××××\");"
                + "station.sellTicket();"
                + "this.takeHandlingFee();"
                + "this.showAlertInfo(\"××××欢迎您的光临，再见！××××\");}");
        cc.addMethod(sellTicket);

        //添加inquire方法
        CtMethod inquire = CtMethod.make("public void inquire() {}", cc);
        inquire.setBody("{this.showAlertInfo(\"××××欢迎光临本代售点，问询服务不会收取任何费用，本问询信息仅供参考，具体信息以车站真实数据为准！××××\");"
                + "station.inquire();"
                + "this.showAlertInfo(\"××××欢迎您的光临，再见！××××\");}"
        );
        cc.addMethod(inquire);

        //添加widthraw方法
        CtMethod withdraw = CtMethod.make("public void withdraw() {}", cc);
        withdraw.setBody("{this.showAlertInfo(\"××××欢迎光临本代售点，退票除了扣除票额的20%外，本代理处额外加收2元手续费！××××\");"
                + "station.withdraw();"
                + "this.takeHandlingFee();}"
        );
        cc.addMethod(withdraw);

        //获取动态生成的class
        Class c = cc.toClass();
        //获取构造器
        Constructor constructor = c.getConstructor(Station.class);
        //通过构造器实例化
        TicketService o = (TicketService) constructor.newInstance(new Station());
        o.inquire();

        cc.writeFile(BYTECODE_PATH);
    }
}
